/*
 * Transmissão de Dados - Trabalho 3
 *
 * Alex Rodrigo de Oliveira      - 06/30403
 * Danilo Gaby Andersen Trindade - 06/82039
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h>
#include "checksum.h"
#include "segments.h"
#include "errors.h"

#define NUMINPUTS 7
#define ADDRINDEX 1
#define BUFFINDEX 2
#define CAPINDEX 3
#define RTTINDEX 4
#define ERRINDEX 5
#define TIMEINDEX 6

static void send_data();
static void *receive_ack();
static void initialize(int n, char **parameters);
static void do_handshake();
static void housekeep(int status);

static int sock;
static struct sockaddr_in server_address;
static uint16_t buffer_size;
static uint32_t rtt;
static uint64_t link_capacity;		/* em bytes por segundo */
static uint16_t error_percentage;
static uint32_t timeout;			/* em milissegundos */
static Segment dummy;
static uint16_t last_sent, last_received;		/* só pode acessar se der lock no mutex */
static pthread_mutex_t ack_mutex = PTHREAD_MUTEX_INITIALIZER;


/*
 * Função main.
 */
int main(int argc, char **argv) {
	initialize(argc, argv);
	do_handshake();
	send_data();
	housekeep(EXIT_SUCCESS);
}


/*
 * Envia todos os pacotes e só termina quando receber o último ack.
 * Usa uma thread para ficar recebendo os acks paralelamente.
 */
void send_data() {
	int cont = 0;
	uint16_t sent_this_second = 0;
	struct timeval last_time;		/* a última vez que recebeu um segmento */
	struct timeval now;

	/* Lança a thread que vai ficar recebendo os acks. */
	pthread_t ack_thread;
	int erro = pthread_create(&ack_thread, NULL, receive_ack, NULL);
	if (erro) {
		perror(ERR_THREAD_MSG);
		housekeep(EXIT_FAILURE);
	}

	/* Fica em loop enviando os pacotes um por um até receber o ack do último
	pacote. */
	while (last_received < NUMPKTS) {
		gettimeofday(&now, NULL);

		/* Pega o lock do mutex para poder acessar as variáveis compartilhadas
		com a thread. */
		pthread_mutex_lock(&ack_mutex);

		/* Se a quantidade de acks que está esperando é menor que o tamanho da
		janela, pode enviar mais um segmento. */
		if (last_sent - last_received < buffer_size) {

			/* Envia o próximo segmento. */
			dummy.seqnum = ++last_sent;
			last_time = now;
			erro = sendto(sock, &dummy, SEGMENT_SIZE, 0,
					(struct sockaddr *) &server_address, sizeof server_address);
			if (erro == -1) {
				perror(ERR_SEND_MSG);
				housekeep(EXIT_FAILURE);
			}
			cont++;

			/* Simula a capacidade do enlace, não enviando mais bps (aqui, no
			 caso, está em Bps) do que ele aguenta. */
			sent_this_second += SEGMENT_SIZE;
			if (sent_this_second > link_capacity) {
				sleep(1);
				sent_this_second = 0;
			}
		}

		/* Se está esperando um ack ser recebido e der timeout, volta o
		last_sent para o início da janela para reenviar ela toda. */
		else if (to_ms(now) - to_ms(last_time) > timeout) {
			last_sent = last_received;
			printf("timeout %d\n", last_sent);
		}

		/* Libera o lock do mutex. */
		pthread_mutex_unlock(&ack_mutex);
	}
	printf("Quantidade de pacotes enviados: %d\n", cont);
}


/*
 * Roda em uma thread separada que fica recebendo os acks e atualizando o
 * last_received.
 */
void *receive_ack() {
	Ack this_ack = {0, 0};
	uint32_t addr_size = sizeof server_address;

	/* Fica em loop infinito recebendo acks. */
	for (;;) {

		/* Espera receber um ack na porta. */
		int erro = recvfrom(sock, &this_ack, sizeof(Ack), 0,
				(struct sockaddr *) &server_address, &addr_size);
		if (erro == -1) {
			perror(ERR_LISTEN_MSG);
			housekeep(EXIT_FAILURE);
		}

		/* Pega o lock do mutex pra poder acessar as variáveis compartilhadas. */
		pthread_mutex_lock(&ack_mutex);

		/* Se receber um ack maior que o último recebido, atualiza o
		last_received para o número desse ack mesmo que faltem alguns acks no
		meio do caminho (entre o último e o atual), pois considera-se que eles
		foram perdidos. Se receber um ack menor ou igual, ignore-o, pois ele só
		chegou atrasado. */
		if (this_ack.ack > last_received) {
			last_received = this_ack.ack;
			printf("ack %d\n", last_received);
		}

		/* Libera o lock do mutex. */
		pthread_mutex_unlock(&ack_mutex);
	}
}


/*
 * Inicializa as variaveis globais.
 */
void initialize(int n, char **parameters) {

	/* Testa a quantidade de parâmetros. */
	if (n != NUMINPUTS) {
		errno = EINVAL;
		perror(ERR_INPUT_MSG);
		housekeep(EXIT_FAILURE);
	}

	/* Inicializa o endereço do servidor. */
	server_address.sin_family = AF_INET;
	server_address.sin_port = htons(SERVER_PORT);
	memset(&server_address.sin_zero, 0, 8);
	server_address.sin_addr.s_addr = inet_addr(parameters[ADDRINDEX]);

	/* Inicializa as variáveis numéricas. */
	buffer_size = atoi(parameters[BUFFINDEX]);
	link_capacity = atoi(parameters[CAPINDEX]) / 8;
	rtt = atoi(parameters[RTTINDEX]);
	error_percentage = atoi(parameters[ERRINDEX]);
	timeout = atoi(parameters[TIMEINDEX]);

	/* Inicializa o socket. */
	sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (sock == -1) {
		perror(ERR_SOCK_MSG);
		housekeep(EXIT_FAILURE);
	}

	/* Cria a mensagem padrão para ser enviada em todos os segmentos. */
	for (int i = 0; i < MSG_SIZE; i++)
		dummy.msg[i] = (i % 92) + 33;
	dummy.msg[MSG_SIZE - 1] = '\0';
	dummy.seqnum = 0;
	dummy.checksum = 0;
	dummy.checksum = calc_checksum(&dummy, sizeof(Segment));
}


/*
 * Envia e recebe os primeiros pacotes entre o cliente e o servidor.
 * No pacote enviado o cliente informa o RTT e a taxa de erro para o servidor
 * simular. O cliente recebe o ACK 0, marcando o bom estabelecimento da conexão.
 */
void do_handshake() {

	/* Cria o segmento e envia. */
	Handshake seg = { rtt, error_percentage };
	int aux = sendto(sock, &seg, sizeof seg, 0,
			(struct sockaddr *) &server_address, sizeof server_address);
	if (aux == -1) {
		perror(ERR_HAND_MSG);
		housekeep(EXIT_FAILURE);
	}

	/* Recebe o ack 0 para dizer que os dados do handshake foram recebidos. */
	Ack ack0;
	uint32_t size = sizeof ack0;
	aux = recvfrom(sock, &ack0, sizeof ack0, 0,
			(struct sockaddr *) &server_address, &size);
	if (aux == -1 || ack0.ack != 0) {
		perror(ERR_HAND_MSG);
		housekeep(EXIT_FAILURE);
	}
}


/*
 * Arruma a casa para sair, i.e., libera memória, fecha streams, etc.
 */
void housekeep(int status) {
	close(sock);
	exit(status);
}


#undef NUMINPUTS
#undef ADDRINDEX
#undef BUFFINDEX
#undef CAPINDEX
#undef RTTINDEX
#undef ERRINDEX
#undef TIMEINDEX
#undef NUMPKTS
